using System;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Web;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Xml.Linq;
using HtmlAgilityPack;
using Microsoft.Http;
using Microsoft.Practices.Composite.Events;
using WpfTwitterClient.Infrastructure.Events;
using WpfTwitterClient.Infrastructure.Models;
using WpfTwitterClient.Infrastructure.Properties;

namespace WpfTwitterClient.Infrastructure.Services
{

    public class TwitterClient : OAuthBase, ITwitterClient
    {
        #region Fields

        private IEventAggregator eventAggregator;
        private ICacheService cacheService;
        public enum Method { GET, POST };
        public const string REQUEST_TOKEN = "http://twitter.com/oauth/request_token";
        public const string AUTHORIZE = "http://twitter.com/oauth/authorize";
        public const string ACCESS_TOKEN = "http://twitter.com/oauth/access_token";

        //private string _consumerKey = "";
        //private string _consumerSecret = "";
        //private string _token = "";
        //private string _tokenSecret = "";
        private string _verifier = "";
        private User _user;

        #endregion

        #region Constructors

        public TwitterClient(IEventAggregator eventAggregator, ICacheService cacheService)
        {
            this.eventAggregator = eventAggregator;
            this.cacheService = cacheService;

        }

        #endregion

        #region Properties

        public string ConsumerKey 
        {
            get { return Settings.Default.ConsumerKey; } 
        }
        
        public string ConsumerSecret { get { return Settings.Default.ConsumerSecret; } }

        public bool IsAuthorized
        {
            get { return Settings.Default.IsAuthorized; }
            set { 
                Settings.Default.IsAuthorized = value;
                Settings.Default.Save();
            }
        }

        public string AccessUrl
        {
            get { return Settings.Default.AccessUrl; }
            set
            {
                Settings.Default.AccessUrl = value;
                Settings.Default.Save();
            }
        }

        public string Token
        {
            get { return Settings.Default.Token; }
            set
            {
                Settings.Default.Token = value;
                Settings.Default.Save();
            }
        }
        public string TokenSecret
        {
            get { return Settings.Default.TokenSecret; }
            set
            {
                Settings.Default.TokenSecret = value;
                Settings.Default.Save();
            }
        }
        public string Verifier { get { return _verifier; } set { _verifier = value; } }

        #endregion

        #region Methods

        public void Initialize()
        {
            try
            {
                var key = ConsumerKey;
            }
            catch (ConfigurationErrorsException e)
            {
                string filename = ((ConfigurationErrorsException)e.InnerException).Filename;
                var info = new FileInfo(filename);
                info.Delete();
                var key = ConsumerKey;
            }

            var user = this.GetUser();
            if (user != null)
            {
                this.eventAggregator.GetEvent<ConnectedEvent>().Publish(string.Empty);
            }
        }

        public User GetUser()
        {
            try
            {
                const string url = "http://twitter.com/account/verify_credentials.xml";
                var response = Get(url);
                response.EnsureStatusIsSuccessful();
                var xml = response.Content.ReadAsXElement();
                _user = GetUser(xml);
                cacheService.Add(url, xml);
                IsAuthorized = true;
                return _user;
            }
            catch (Exception)
            {
                IsAuthorized = false;
                return null;
            }
        }

        public void Reset()
        {
            Token = string.Empty;
            TokenSecret = string.Empty;
            IsAuthorized = false;

            eventAggregator.GetEvent<NotAuthorizedEvent>().Publish("");
            eventAggregator.GetEvent<ApplicationResetEvent>().Publish("");
            eventAggregator.GetEvent<StatusEvent>().Publish("Application has been reset");
        }

        public Tweets BeginGetHomeTimeline(BackgroundWorker bw)
        {
            const string uri = "http://api.twitter.com/1/statuses/home_timeline.xml";

            bw.ReportProgress(5);
            XElement xml = null;//cacheService.Get(uri) as XElement;
            bw.ReportProgress(10);
            if (xml == null)
            {
                var response = Get(uri);
                bw.ReportProgress(15);

                response.EnsureStatusIsSuccessful();
                bw.ReportProgress(20);

                xml = response.Content.ReadAsXElement();
                bw.ReportProgress(25);

                cacheService.Add(uri, xml);
            }
            bw.ReportProgress(50);

            return GetTweets(xml);
        }

        public Tweets GetHomeTimeline()
        {
            const string uri = "http://api.twitter.com/1/statuses/home_timeline.xml";

            XElement xml = null;//cacheService.Get(uri) as XElement;
            if(xml == null)
            {
                var response = Get(uri);
                response.EnsureStatusIsSuccessful();
                xml = response.Content.ReadAsXElement();
                cacheService.Add(uri, xml);
            }

            return GetTweets(xml);
        }

        private Tweets GetTweets(XElement xml)
        {
            var statuses = xml.Descendants("status");
            var tweets = new Tweets();
            foreach (XElement status in statuses)
            {
                try
                {
                    var userElement = status.Element("user");
                    User user = GetUser(userElement);

                    bool following;
                    bool parsed = Boolean.TryParse(userElement.Element("following").Value, out following);
                    if (parsed)
                        user.Following = following;

                    var tweet = new Tweet
                                    {
                                        Text = status.Element("text").Value,
                                        User = user,
                                        Source = status.Element("source").Value
                                    };


                    DateTime date = status.Element("created_at").Value.ParseDateTime();
                    TimeSpan age = DateTime.Now - date;
                    if (age.TotalDays > 2)
                        tweet.Date = string.Format("{0} days ago", (int)age.TotalDays);
                    else if (age.TotalHours > 1)
                        tweet.Date = string.Format("{0} hours ago", (int)age.TotalHours);
                    else if (age.TotalMinutes > 1)
                        tweet.Date = string.Format("{0} minutes ago", (int)age.TotalMinutes);
                    else
                        tweet.Date = string.Format("{0} seconds ago", (int)age.TotalSeconds);

                    FlowDocument doc = GetDoc(user, tweet);
                    if(doc == null)
                        throw new ArgumentNullException("doc");
                    tweet.Document = doc;
                    tweets.Add(tweet);
                }
                catch (Exception)
                {

                    throw;
                }
            }
            return tweets;
        }

        private User GetUser(XElement userElement)
        {
            return new User
                       {
                           ScreenName = userElement.Element("screen_name").Value,
                           Name = userElement.Element("name").Value,
                           ProfileImageUrl = userElement.Element("profile_image_url").Value
                       };
        }

        private FlowDocument GetDoc(User user, Tweet tweet)
        {
            var doc = new FlowDocument {PagePadding = new Thickness(0)};
            var para = new Paragraph
               {
                    FontSize = 12,
                    FontFamily = new FontFamily("Verdana,Arial,sans-serif"),
                    TextAlignment = TextAlignment.Left
               };
            var run = new Run(user.ScreenName);
            var hl = new Hyperlink(run)
                {
                     NavigateUri = new Uri(string.Format("http://twitter.com/{0}", user.ScreenName))
                     //Foreground = new SolidColorBrush(Colors.Blue)
                };
            hl.RequestNavigate += new RequestNavigateEventHandler(hl_RequestNavigate);
            var bold = new Bold(hl);
            para.Inlines.Add(bold);
            para.Inlines.Add(" ");

            var tweetText = tweet.Text;
            FormatPara(ref para, tweetText);

            var lb = new LineBreak();
            para.Inlines.Add(lb);

            var sourceHtml = new HtmlDocument();
            sourceHtml.LoadHtml(tweet.Source);

            run = new Run(string.Format("{0} {1} via {2}", user.Name, tweet.Date, sourceHtml.DocumentNode.InnerText))
                      {
                          Foreground = new SolidColorBrush(Colors.Gray),
                          FontStyle = FontStyles.Italic
                      };

            para.Inlines.Add(run);

            doc.Blocks.Add(para);
            return doc;
        }

        private void FormatPara(ref Paragraph para, string text)
        {

            var doc = new HtmlDocument();
            doc.LoadHtml(text);
            var anchors = doc.DocumentNode.SelectNodes("//a[@href]");
            if(anchors != null)
            {
                foreach (HtmlNode link in anchors)
                {
                    HtmlAttribute att = link.Attributes["href"];
                    if (att != null)
                        link.InnerHtml = string.Format("[link:{0}>>>{1}]", link.InnerText, att.Value);
                }
            }

            var parts = doc.DocumentNode.InnerText.Split(new char[] { ' ', '[', ']' }).ToList();
            foreach (var part in parts)
            {
                if(part.StartsWith("http://"))
                {
                    var run = new Run(part) {Cursor = Cursors.Hand};
                    var hl = new Hyperlink(run)
                                 {
                                     NavigateUri = new Uri(part)
                                     //Foreground = new SolidColorBrush(Colors.Blue)
                                 };
                    hl.RequestNavigate += new System.Windows.Navigation.RequestNavigateEventHandler(hl_RequestNavigate);
                    para.Inlines.Add(hl);
                } else if(part.StartsWith("link:"))
                {
                    var txt = part.ChopLeft("link:");
                    var pieces = txt.SplitWithStringSeparator(">>>");
                    var linkText = pieces[0];
                    var linkUrl = pieces[1];
                    var run = new Run(linkText) { Cursor = Cursors.Hand };
                    var hl = new Hyperlink(run)
                    {
                        NavigateUri = new Uri(linkUrl),
                        Foreground = new SolidColorBrush(Colors.Blue)
                    };
                    hl.RequestNavigate += new System.Windows.Navigation.RequestNavigateEventHandler(hl_RequestNavigate);
                    para.Inlines.Add(hl);
                }
                else
                {
                    para.Inlines.Add(part);
                }
                para.Inlines.Add(" ");
            }
        }

        protected void hl_RequestNavigate(object sender, RequestNavigateEventArgs e)
        {
            Process.Start(e.Uri.ToString());
        }

        /// <summary>
        /// Get the link to Twitter's authorization page for this application.
        /// </summary>
        /// <returns>The url with a valid request token, or a null string.</returns>
        public string GetRequestToken()
        {
            string ret = null;
            this.Token = null;
            this.TokenSecret = null;
            this.IsAuthorized = false;
            var response = this.Get(REQUEST_TOKEN);
            var responseContent = response.Content.ReadAsString();
            if (responseContent.Length > 0)
            {
                //response contains token and token secret.  We only need the token.
                NameValueCollection qs = HttpUtility.ParseQueryString(responseContent);
                if (qs["oauth_token"] != null)
                {
                    ret = AUTHORIZE + "?oauth_token=" + qs["oauth_token"];
                }
            }

            this.AccessUrl = ret;

            var uri = new Uri(ret);
            this.Token = HttpUtility.ParseQueryString(uri.Query)["oauth_token"];
            
            return ret;
        }

        /// <summary>
        /// Exchange the request token for an access token.
        /// </summary>
        /// <param name="authToken">The oauth_token is supplied by Twitter's authorization page following the callback.</param>
        public bool GetAccessToken(string verifier)
        {
            try
            {
                this.Verifier = verifier;

                var response = Get(ACCESS_TOKEN);
                response.EnsureStatusIsSuccessful();
                var content = response.Content.ReadAsString();

                //Store the Token and Token Secret
                NameValueCollection qs = HttpUtility.ParseQueryString(content);
                if (qs["oauth_token"] != null)
                {
                    this.Token = qs["oauth_token"];
                }
                if (qs["oauth_token_secret"] != null)
                {
                    this.TokenSecret = qs["oauth_token_secret"];
                }
                this.IsAuthorized = true;
                //var user = GetUser();
                //if(user != null)
                    return true;
            }
            catch (Exception)
            {

                return false;
            }
        }

        public HttpResponseMessage Get(string url)
        {
            return Get(Method.GET, url, string.Empty);    
        }

        public HttpResponseMessage Post(string url, string postData)
        {
            return Get(Method.POST, url, postData);    
            
        }

        private HttpResponseMessage Get(Method method, string url, string postData)
        {
            string outUrl;
            string querystring;
            HttpResponseMessage ret;

            //Setup postData for signing.
            //Add the postData to the querystring.
            if (method == Method.POST)
            {
                if (postData.Length > 0)
                {
                    //Decode the parameters and re-encode using the oAuth UrlEncode method.
                    NameValueCollection qs = HttpUtility.ParseQueryString(postData);
                    postData = "";
                    foreach (string key in qs.AllKeys)
                    {
                        if (postData.Length > 0)
                        {
                            postData += "&";
                        }
                        qs[key] = HttpUtility.UrlDecode(qs[key]);
                        qs[key] = UrlEncode(qs[key]);
                        postData += key + "=" + qs[key];

                    }
                    if (url.IndexOf("?") > 0)
                    {
                        url += "&";
                    }
                    else
                    {
                        url += "?";
                    }
                    url += postData;
                }
            }

            var uri = new Uri(url);
            
            string nonce = GenerateNonce();
            string timeStamp = GenerateTimeStamp();

            //Generate Signature
            string sig = GenerateSignature(uri,
                ConsumerKey,
                ConsumerSecret,
                Token,
                TokenSecret,
                Verifier,
                method.ToString(),
                timeStamp,
                nonce,
                out outUrl,
                out querystring, "oob");

            querystring += "&oauth_signature=" + HttpUtility.UrlEncode(sig);

            //Convert the querystring to postData
            if (method == Method.POST)
            {
                postData = querystring;
                querystring = "";
            }

            if (querystring.Length > 0)
            {
                outUrl += "?";
            }

            ret = WebRequest(method, outUrl +  querystring, postData);

            if(ret.StatusCode == HttpStatusCode.Unauthorized)
                eventAggregator.GetEvent<NotAuthorizedEvent>().Publish(null);

            return ret;
        }

        /// <summary>
        /// Web Request Wrapper
        /// </summary>
        /// <param name="method">Http Method</param>
        /// <param name="url">Full url to the web resource</param>
        /// <param name="postData">Data to post in querystring format</param>
        /// <returns>The web server response.</returns>
        private static HttpResponseMessage WebRequest(Method method, string url, string postData)
        {
            using (var client = new HttpClient(url))
            {
                HttpResponseMessage response = null;
                switch (method)
                {
                    case Method.GET:
                        response = client.Get();
                        break;
                    case Method.POST:
                        //response = client.Post(postData);
                        break;
                }
                //response.EnsureStatusIsSuccessful();
                return response;
            }

            //if (method == Method.POST)
            //{
            //    webRequest.ContentType = "application/x-www-form-urlencoded";

            //    //POST the data.
            //    requestWriter = new StreamWriter(webRequest.GetRequestStream());
            //    try
            //    {
            //        requestWriter.Write(postData);
            //    }
            //    catch
            //    {
            //        throw;
            //    }
            //    finally
            //    {
            //        requestWriter.Close();
            //        requestWriter = null;
            //    }
            //}
        }

        #endregion
    }
}
